using System;
using System.IO;
using System.Data;
using System.Xml.Serialization;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

namespace NNFramework.NeuroLibrary
{
    [Serializable]
    [XmlRootAttribute("NeuronalNetwork", Namespace = clsBase.STR_DFT_NS, IsNullable = true)]
    public class clsNetwork<TMainInput, TNConnect, TMainOutput> : clsBase
        where TMainInput: struct
        where TNConnect: struct 
        where TMainOutput: struct
    {        

        #region Delegates Section

        public delegate void mdlgLearnFx(clsNetwork<TMainInput, TNConnect, TMainOutput> pntkNetwork);
        public delegate double mdlgErrorFx(clsNetwork<TMainInput, TNConnect, TMainOutput> pntkNetwork);

        #endregion

        #region Members Section

        private NetworkArchitecture mnaArchitecture;
        private List<clsLayer<TMainInput, TNConnect>> milyInLayers;
        private List<clsLayer<TNConnect, TNConnect>> milyHidLayers;
        private List<clsLayer<TNConnect, TMainOutput>> milyOutLayers;        
        private Collection<TMainOutput> mlstCurrentOutput; 
        private mdlgLearnFx mLearningFx;
        private mdlgErrorFx mErrorFx;
        private DataTable mdtbExamples;
        private double mdblError;
        private TimeSpan mtmsTrainSpan;        

        #endregion      


        #region Properties Section

        public double ActualError
        {
            get
            {
                return mdblError;
            }
        }

        public TimeSpan LastTrainTime
        {
            get
            {
                return mtmsTrainSpan;
            }
        }

        public List<clsLayer<TMainInput, TNConnect>> InputLayers
        {
            get
            {
                return milyInLayers;
            }
        }

        public List<clsLayer<TNConnect, TNConnect>> HiddenLayers
        {
            get
            {
                return milyHidLayers;
            }
        }

        public List<clsLayer<TNConnect, TMainOutput>> OutputLayers
        {
            get
            {
                return milyOutLayers;
            }

        }

        public Collection<TMainOutput> CurrentOutputs
        {
          get 
          {
              GetOutputs();
              return mlstCurrentOutput; 
          }          
        }

        public DataTable Examples
        {
            get 
            { 
                return mdtbExamples; 
            }
            set
            {
                value.TableName = STR_DFT_TBLEXMNAM;
                value.Namespace = STR_DFT_NS;
                mdtbExamples = value; 
            }
        }

        #endregion


        #region Constructors Section

        public clsNetwork()
        {
            milyInLayers = new List<clsLayer<TMainInput,TNConnect>>();
            milyHidLayers = new List<clsLayer<TNConnect,TNConnect>>();
            milyOutLayers = new List<clsLayer<TNConnect,TMainOutput>>();
            mLearningFx = null;
            mnaArchitecture = NetworkArchitecture.Other;
            mErrorFx = clsErrorFx<TMainInput, TNConnect, TMainOutput>.BasicErrorFx;
        }

        public clsNetwork(mdlgLearnFx pdelLearningFunction)
        {
            milyInLayers = new List<clsLayer<TMainInput, TNConnect>>();
            milyHidLayers = new List<clsLayer<TNConnect, TNConnect>>();
            milyOutLayers = new List<clsLayer<TNConnect, TMainOutput>>();
            mLearningFx = pdelLearningFunction;
            mnaArchitecture = NetworkArchitecture.Other;
            mErrorFx = clsErrorFx<TMainInput, TNConnect, TMainOutput>.BasicErrorFx;
        }

        public clsNetwork(mdlgLearnFx pdelLearningFunction, NetworkArchitecture pnaArchitect)
        {
            milyInLayers = new List<clsLayer<TMainInput, TNConnect>>();
            milyHidLayers = new List<clsLayer<TNConnect, TNConnect>>();
            milyOutLayers = new List<clsLayer<TNConnect, TMainOutput>>();
            mLearningFx = pdelLearningFunction;
            mnaArchitecture = pnaArchitect;
            mErrorFx = clsErrorFx<TMainInput, TNConnect, TMainOutput>.BasicErrorFx;
        }

        public clsNetwork(mdlgLearnFx pdelLearningFunction, mdlgErrorFx pdelErrorFunction, NetworkArchitecture pnaArchitect)
        {
            milyInLayers = new List<clsLayer<TMainInput, TNConnect>>();
            milyHidLayers = new List<clsLayer<TNConnect, TNConnect>>();
            milyOutLayers = new List<clsLayer<TNConnect, TMainOutput>>();
            mLearningFx = pdelLearningFunction;
            mnaArchitecture = pnaArchitect;
            mErrorFx = pdelErrorFunction;
        }

        #endregion


        #region Public Methods

        public void AddInputLayer(clsLayer<TMainInput, TNConnect> clyLayer)
        {
            milyInLayers.Add(clyLayer);
        }

        public void AddHiddenLayer(clsLayer<TNConnect, TNConnect> clyLayer)
        {
            milyHidLayers.Add(clyLayer);
        }

        public void AddOutputLayer(clsLayer<TNConnect, TMainOutput> clyLayer)
        {
            milyOutLayers.Add(clyLayer);            
        }

        public object GetNeuronFromGUID(Guid puidID)
        {
            object objResult = null;

            Predicate<clsNeuron<TMainInput, TNConnect>> ToMatchInput = delegate(clsNeuron<TMainInput, TNConnect> NeuronToCompare)
            {
                bool blnResult = false;
                if (puidID != null)
                {
                    if (NeuronToCompare.GUID == puidID)
                    {
                        blnResult = true;
                    }
                }
                return blnResult;
            };

            foreach (clsLayer<TMainInput, TNConnect> lyrInput in milyInLayers)
            {
                objResult = lyrInput.Neurons.Find(ToMatchInput);                                
            }

            if (objResult == null)
            {
                Predicate<clsNeuron<TNConnect, TNConnect>> ToMatchHidden = delegate(clsNeuron<TNConnect, TNConnect> NeuronToCompare)
                    {
                        bool blnResult = false;
                        if (puidID != null)
                        {
                            if (NeuronToCompare.GUID == puidID)
                            {
                                blnResult = true;
                            }
                        }
                        return blnResult;
                    };

                foreach (clsLayer<TNConnect, TNConnect> lyrHidden in milyHidLayers)
                {
                    objResult = lyrHidden.Neurons.Find(ToMatchHidden);
                }

                if (objResult == null)
                {
                    Predicate<clsNeuron<TNConnect, TMainOutput>> ToMatchOutPut = delegate(clsNeuron<TNConnect, TMainOutput> NeuronToCompare)
                            {
                                bool blnResult = false;
                                if (puidID != null)
                                {
                                    if (NeuronToCompare.GUID == puidID)
                                    {
                                        blnResult = true;
                                    }
                                }
                                return blnResult;
                            };
                    foreach (clsLayer<TNConnect, TMainOutput> lyrOutput in milyOutLayers)
                    {
                        objResult = lyrOutput.Neurons.Find(ToMatchOutPut);
                    }
                }
            }
            return objResult; 
        }

        public void TrainMeOnce()
        {
            DateTime dtmStart = DateTime.Now;
            mLearningFx(this);
            mtmsTrainSpan = dtmStart - DateTime.Now;
        }

        #endregion

        #region Internal Section

        private void GetOutputs()
        {
            if (milyOutLayers.Count > 0)
            {
                mlstCurrentOutput = new Collection<TMainOutput>();
                foreach (clsLayer<TNConnect, TMainOutput> oplLayer in milyOutLayers)
                {
                    foreach (clsNeuron<TNConnect, TMainOutput> opnNeuron in oplLayer.Neurons)
                    {
                        mlstCurrentOutput.Add(opnNeuron.Axon);                        
                    }                    
                }                
            }
            else
            {
                mlstCurrentOutput = null;
            }
        }

        private DataTable GetInputsEmptyTable()
        {
            DataTable dtbResult = null;
            if (milyInLayers.Count > 0)
            {
                int intColumns = GetInputsNumber();
                if (intColumns > 0)
                {
                    dtbResult = new DataTable();
                    for (int i = 0; i < intColumns; i++)
                    {
                        dtbResult.Columns.Add("I" + i, typeof(TMainInput));
                    }                     
                }                               
            }
            return dtbResult;
        }

        private void GetError()
        {
            //yes you're right you can directly return the error in the get method of the property, but if you done that it can't be serialized
            mdblError = mErrorFx(this);
        }        

        private int GetInputsNumber()
        {
            int intColumns = 0;
            foreach (clsLayer<TMainInput, TNConnect> inlLayer in milyInLayers)
            {
                foreach (clsNeuron<TMainInput, TNConnect> ipnNeuron in inlLayer.Neurons)
                {
                    intColumns += ipnNeuron.DendritesCount;
                    switch (inlLayer.Architecture)
                    {
                        case LayerArchitecture.NonLateral:
                        case LayerArchitecture.Other:
                            intColumns += ipnNeuron.DendritesCount;
                            break;
                        case LayerArchitecture.FullLateralNoRecursive:
                        case LayerArchitecture.FullLateralRecursive:
                            intColumns++;
                            break;
                        default:
                            break;
                    }
                }
            }
            return intColumns;
        }        
        #endregion

        #region Public Static Methods Section

        public static clsNetwork<TMainInput, TNConnect, TMainOutput> LoadNetworkFromXml(string pstrFileName)
        {
            XmlSerializer xslSerializer = new XmlSerializer(typeof(clsNetwork<TMainInput, TNConnect, TMainOutput>));
            FileStream flsFile = new FileStream(pstrFileName, FileMode.Open);
            return (clsNetwork<TMainInput, TNConnect, TMainOutput>)xslSerializer.Deserialize(flsFile);
        }

        public static clsNetwork<TMainInput, TNConnect, TMainOutput> LoadNetworkFromBin(string pstrFileName)
        {
            IFormatter iftBinary = new BinaryFormatter();
            Stream stmFile = new FileStream(pstrFileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
            return (clsNetwork<TMainInput, TNConnect, TMainOutput>)iftBinary.Deserialize(stmFile);
        }

        #endregion
    }
}
